package transaction;

import java.io.*;

import utils.Constants;
import utils.Converter;

import java.util.*;
import java.util.Map.Entry;

import triStorage.*;

public class Recovery {

	DataInputStream reader;
	
	//HashSet<Integer> uncommitTx;
	
	HashMap<Integer, Vector<LogItem>> logs;
	
	TriMMPFile[] mmpfiles;
	
	Transaction tx;
	
	public Recovery(Transaction tx) {
		this.tx = tx;
		
		//uncommitTx = new HashSet<Integer>();
	}
	
	private boolean init() {
		File f = new File(Constants.LOG_FILENAME);
		if (!f.exists())
			return false;
		
		try {
			FileInputStream stream = new FileInputStream(Constants.LOG_FILENAME);
			BufferedInputStream bstream = new BufferedInputStream(stream, 4096);
			reader = new DataInputStream(bstream);
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		int cnt;
		try {
			cnt = reader.readInt();
			mmpfiles = new TriMMPFile[cnt];
			for (int i = 0; i < cnt; ++i) {
				int len = reader.readInt();
				byte[] buffer = new byte[len];
				reader.read(buffer, 0, len);
				
				int idx = reader.readInt();
				
				String filename = Converter.Bytes2String(buffer, 0);
				mmpfiles[idx] = createTriMMPFile(filename);
				System.out.println(filename + " : " + idx);
				mmpfiles[idx].open();
			}
			logs = new HashMap<Integer, Vector<LogItem>> ();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return true;
	}
	
	public void recover() {
		if (!TxManager.EnableRecovery || !init())
			return;
		System.out.println("redo");
		redo();
		System.out.println("undo " + logs.size());
		undo();
		close();
	}
	
	public void undo() {
		Iterator<Entry <Integer, Vector<LogItem>>> itr = logs.entrySet().iterator();
		while (itr.hasNext()) {
			Entry<Integer, Vector<LogItem>> entry = itr.next();
			int txNum = entry.getKey();
			Vector<LogItem> logItems = entry.getValue();
			System.out.println("undo " + txNum);
			for (int i = logItems.size() - 1; i >= 0; --i) {
				LogItem item = logItems.get(i);
				
				switch (item.type) {
				case TxManager.AppendInt:
					this.undoAppendInt(item.fileId, item.pageId, item.offset, (Integer)item.newValue);
					break;
				case TxManager.AppendLong:
					this.undoAppendLong(item.fileId, item.pageId, item.offset, (Long)item.newValue);
					break;
				case TxManager.AppendBytes:
					byte[] buffer = (byte[])item.newValue;
					this.undoAppendBytes(item.fileId, item.pageId, item.offset, buffer, 0, buffer.length);
					break;
				case TxManager.WriteInt:
					this.undoWriteInt(item.fileId, item.pageId, item.offset, (Integer)item.oldValue,
							(Integer)item.newValue);
					break;
				case TxManager.WriteLong:
					this.undoWriteLong(item.fileId, item.pageId, item.offset, (Long)item.oldValue, 
							(Long)item.newValue);
					break;
				}
			}
		}
	}
	
	public void redo() {
		try {
			while (true) {
				int type = reader.readInt();
				int txNum = reader.readInt();
				//System.out.println("TXNUM : " + txNum + " " + type);
				
				if (type == TxManager.TxBegin) {
					//		uncommitTx.add(txNum);
					//System.out.println("TxBegin " + txNum);
					logs.put(txNum, new Vector<LogItem>());
					continue;
				}
				else if (type == TxManager.TxCommit) {
					//	uncommitTx.remove(txNum);
					//System.out.println("TxCommit " + txNum);
					logs.remove(txNum);
					continue;
				}
				
				int fileId = reader.readInt();
				int pageId = reader.readInt();
				int offset = reader.readInt();
				
				Integer oldInt, newInt;
				Long oldLong, newLong;

				switch (type) {
				case TxManager.AppendInt:
					newInt = reader.readInt();
					this.redoAppendInt(fileId, pageId, offset, newInt);
					logs.get(txNum).add(new LogItem(type, fileId, pageId, offset, null, newInt));
					break;
				case TxManager.AppendLong:
					newLong = reader.readLong();
					this.redoAppendLong(fileId, pageId, offset, newLong);
					logs.get(txNum).add(new LogItem(type, fileId, pageId, offset, null, newLong));
					break;
				case TxManager.AppendBytes:
					int len = reader.readInt();
					byte[] buffer = new byte[len];
					reader.read(buffer, 0, len);
					this.redoAppendBytes(fileId, pageId, offset, buffer, 0, len);
					logs.get(txNum).add(new LogItem(type, fileId, pageId, offset, null, buffer));
					break;
				case TxManager.WriteInt:
					oldInt = reader.readInt();
					newInt = reader.readInt();
					this.redoWriteInt(fileId, pageId, offset, oldInt, newInt);
					logs.get(txNum).add(new LogItem(type, fileId, pageId, offset, oldInt, newInt));
					break;
				case TxManager.WriteLong:
					oldLong = reader.readLong();
					newLong = reader.readLong();
					this.redoWriteLong(fileId, pageId, offset, oldLong, newLong);
					logs.get(txNum).add(new LogItem(type, fileId, pageId, offset, oldLong, newLong));
					break;
				}
			}
		}
		catch (EOFException e) {
			return;
		}
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	
	private void redoAppendInt(int fileId, int pageId, int offset, int value) {
		mmpfiles[fileId].appendInt(pageId, offset, value, tx);
	}
	
	private void redoAppendLong(int fileId, int pageId, int offset, long value) {
		mmpfiles[fileId].appendLong(pageId, offset, value, tx);
	}
	
	private void redoAppendBytes(int fileId, int pageId, int offset, 
			byte[] value, int start, int length) {
		mmpfiles[fileId].appendBytes(pageId, offset, value, start, length, tx);
	}
	
	private void redoWriteInt(int fileId, int pageId, int offset, int oldValue, int newValue) {
		mmpfiles[fileId].writeInt(pageId, offset, newValue, tx);
	}
	
	private void redoWriteLong(int fileId, int pageId, int offset, long oldValue, long newValue) {
		mmpfiles[fileId].writeLong(pageId, offset, newValue, tx);
	}
	
		
	private void undoAppendInt(int fileId, int pageId, int offset, int value) {
		//mmpfiles[fileId].appendInt(pageId, offset, value, tx);
	}
	
	private void undoAppendLong(int fileId, int pageId, int offset, long value) {
		//mmpfiles[fileId].appendLong(pageId, offset, value, tx);
	}
	
	private void undoAppendBytes(int fileId, int pageId, int offset, 
			byte[] value, int start, int length) {
		//mmpfiles[fileId].appendBytes(pageId, offset, value, start, length, tx);
	}
	
	private void undoWriteInt(int fileId, int pageId, int offset, int oldValue, int newValue) {
		mmpfiles[fileId].writeInt(pageId, offset, oldValue, tx);
	}
	
	private void undoWriteLong(int fileId, int pageId, int offset, long oldValue, long newValue) {
		mmpfiles[fileId].writeLong(pageId, offset, oldValue, tx);
	}
	
	/*
	 * FileMap.put(Constants.NODE_FILENAME, 0);
		FileMap.put(Constants.SET_FILENAME, 1);
		FileMap.put(Constants.SET_FILENAME_TEMP, 2);
		FileMap.put(Constants.SET_INODE_FILENAME, 3);
		FileMap.put(Constants.SET_INODE_FILENAME_TEMP, 4);
		FileMap.put(Constants.NODE_IDX_FILENAME, 5);
		FileMap.put(Constants.NODE_IDX_META_FILENAME, 6);
		FileMap.put(Constants.TAG_IDX_FILENAME, 7);
		FileMap.put(Constants.TAG_IDX_FILENAME_TEMP, 8);
		FileMap.put(Constants.TAG_IDX_INODE_FILENAME, 9);
		FileMap.put(Constants.TAG_IDX_INODE_FILENAME_TEMP, 10);
		FileMap.put(Constants.TAG_BPTree_FILENAME, 11);
		FileMap.put(Constants.ITEM_IDX_FILENAME, 12);
		FileMap.put(Constants.ITEM_IDX_INODE_FILENAME, 13);
		FileMap.put(Constants.ITEM_BPTree_FILENAME, 14);
	 */
	private TriMMPFile createTriMMPFile(String filename) {
		if (filename.equals(Constants.NODE_FILENAME)) 
			return new TriMMPFile(filename, 10, Constants.NODE_PAGESIZE);
		else if (filename.equals(Constants.SET_FILENAME))
			return new TriMMPFile(filename, 10, Constants.SET_PAGESIZE);
		else if (filename.equals(Constants.SET_FILENAME_TEMP))
			return new TriMMPFile(filename, 10,	Constants.SET_PAGESIZE);
		else if (filename.equals(Constants.SET_INODE_FILENAME))
			return new TriMMPFile(filename, 10,	Constants.SET_INODE_PAGESIZE);
		else if (filename.equals(Constants.SET_INODE_FILENAME_TEMP))
			return new TriMMPFile(filename, 10,	Constants.SET_INODE_PAGESIZE);
		else if (filename.equals(Constants.NODE_IDX_FILENAME))
			return new TriMMPFile(filename, 10,	Constants.NODE_IDX_PAGESIZE);
		else if (filename.equals(Constants.NODE_IDX_META_FILENAME))
			return new TriMMPFile(filename, 10, Constants.NODE_IDX_META_PAGESIZE);
		else if (filename.equals(Constants.TAG_IDX_FILENAME))
			return new TriMMPFile(filename, 10, Constants.TAG_IDX_PAGESIZE);
		else if (filename.equals(Constants.TAG_IDX_FILENAME_TEMP))
			return new TriMMPFile(filename, 10, Constants.TAG_IDX_PAGESIZE);
		else if (filename.equals(Constants.TAG_IDX_INODE_FILENAME))
			return new TriMMPFile(filename, 10, Constants.TAG_IDX_INODE_PAGESIZE);
		else if (filename.equals(Constants.TAG_IDX_INODE_FILENAME_TEMP))
			return new TriMMPFile(filename, 10, Constants.TAG_IDX_INODE_PAGESIZE);
		else if (filename.equals(Constants.TAG_BPTree_FILENAME))
			return new TriMMPFile(filename, 10, Constants.TAG_BPTree_PAGESIZE);
		else if (filename.equals(Constants.ITEM_IDX_FILENAME))
			return new TriMMPFile(filename, 10, Constants.ITEM_IDX_PAGESIZE);
		else if (filename.equals(Constants.ITEM_IDX_INODE_FILENAME))
			return new TriMMPFile(filename, 10, Constants.ITEM_IDX_INODE_PAGESIZE);
		else if (filename.equals(Constants.ITEM_BPTree_FILENAME))
			return new TriMMPFile(filename, 10, Constants.ITEM_BPTree_PAGESIZE);
		return null;
	}
		
	private void close() {
		try {
			reader.close();
			for (int i = 0; i < mmpfiles.length; ++i)
				mmpfiles[i].close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		/*
		File f = new File(Constants.LOG_FILENAME);
		if (f.exists())
			f.delete();
			*/
	}
}
